package com.wiiss.quartz.util;

import com.wiiss.common.constant.Constants;
import com.wiiss.common.constant.ScheduleConstants;
import com.wiiss.common.utils.redis.RedisCache;
import com.wiiss.common.utils.text.Convert;
import com.wiiss.common.exception.BaseException;
import com.wiiss.common.utils.DateUtils;
import com.wiiss.common.utils.ExceptionUtil;
import com.wiiss.common.utils.StringUtils;
import com.wiiss.common.utils.bean.BeanUtils;
import com.wiiss.common.utils.spring.SpringUtils;
import com.wiiss.common.utils.uuid.IdUtils;
import com.wiiss.quartz.domain.SysJob;
import com.wiiss.quartz.domain.SysJobLog;
import com.wiiss.quartz.mapper.SysJobMapper;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;

import java.util.Date;

/**
 * 抽象quartz调用
 *
 * @author my
 */
@Slf4j
public abstract class AbstractQuartzJob implements Job {

    /**
     * 线程本地变量
     */
    private static ThreadLocal<Date> threadLocal = new ThreadLocal<>();

    @Override
    public void execute(JobExecutionContext context) {
        SysJob sysJob = new SysJob();
        BeanUtils.copyBeanProp(sysJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES));
        Exception exception = null;
        try {
            before();
            if (!this.getPermission(context, sysJob)) {
                // 没有获取到锁，或任务已经执行过
//                log.info("没有获取到锁，或任务已经执行过");
                return;
            }
            doExecute(context, sysJob);
        } catch (Exception e) {
            log.error("任务执行异常  - ：", e);
            exception = e;
        } finally {
            after(context, sysJob, exception);
        }
    }

    /**
     * 执行前
     */
    protected void before() {
        threadLocal.set(new Date());
    }

    /**
     * 是否允许执行：true-允许执行。
     * 集群场景获取到锁时，允许执行;
     * 非集群场景允许执行;
     */
    private boolean getPermission(JobExecutionContext context, SysJob sysJob) {
        // 是否允许执行任务
        boolean isAllow;
        if ("0".equals(sysJob.getIsClustered())) {
            log.info("[{}]以集群模式执行定时任务", sysJob.getJobName());
            //集群方式执行，设置分布式锁的锁值，防止释放锁时因为锁过期时释放了别人的锁
            String lockValue = IdUtils.simpleUUID();
            sysJob.setLockValue(lockValue);
            // 如果是集群模式，则获取分布式锁，保证只有一条线程执行
            int expireTime = 20;
            String quartzJobExpireTime = Convert.toStr(SpringUtils.getBean(RedisCache.class).getCacheObject("sys_config:quartzJobExpireTime"));
            if (StringUtils.isNotEmpty(quartzJobExpireTime)) {
                expireTime = Integer.parseInt(quartzJobExpireTime);
            }
            log.info("开始获取分布式锁,key:{},value:{},expireTime:{}", sysJob.getInvokeTarget(), sysJob.getLockValue(), expireTime);
            Boolean lock = SpringUtils.getBean(RedisCache.class).getLock(sysJob.getInvokeTarget(), sysJob.getLockValue(), expireTime);
            if (lock) {
//                log.info("[{}]开始执行", sysJob.getJobName());
//                isAllow = true;
                log.info("[{}]获取到分布式锁", sysJob.getJobName());
                // 判断任务是否已经执行过了，即当前调度时间<下次执行时间
                Date scheduledFireTime = context.getScheduledFireTime();
                log.info("当前调度时间：" + DateUtils.getStandardTime(scheduledFireTime));
                Date nextFireTime = sysJob.getNextFireTime();
                log.info("job中记录的下次执行时间：" + DateUtils.getStandardTime(nextFireTime));
                if (nextFireTime == null) {
                    // 证明未执行过，可以直接执行
                    log.info("[{}]开始执行", sysJob.getJobName());
                    isAllow = true;
                } else if (scheduledFireTime.compareTo(nextFireTime) == -1) {
                    // compareTo()方法的返回值，scheduledFireTime小于nextFireTime返回-1，scheduledFireTime大于nextFireTime返回1，相等返回0
                    // 证明已经在别的线程中执行过了
                    log.info("[{}]任务的触发时间[{}]小于任务预期执行时间[{}]", sysJob.getJobName(), DateUtils.getStandardTime(scheduledFireTime)
                            , DateUtils.getStandardTime(nextFireTime));
                    isAllow = false;
                } else {
                    // 调度时间等于任务预期执行时间，可以执行
                    log.info("[{}]开始执行", sysJob.getJobName());
                    isAllow = true;
                }
            } else {
                log.info("[{}]未获取到分布式锁，不允许执行", sysJob.getJobName());
                isAllow = false;
            }
        } else if ("1".equals(sysJob.getIsClustered())) {
            log.info("[{}]非集群模式定时任务，直接执行", sysJob.getJobName());
            isAllow = true;
        } else {
            throw new BaseException("未获取到定时任务" + sysJob.getJobName() + "的IsClustered属性");
        }
        sysJob.setAllow(isAllow);
        return isAllow;
    }

    /**
     * 执行后
     *
     * @param context 工作执行上下文对象
     * @param sysJob  系统计划任务
     */
    protected void after(JobExecutionContext context, SysJob sysJob, Exception e) {
        Date startTime = threadLocal.get();
        threadLocal.remove();

        if ("0".equals(sysJob.getIsClustered())) {
            log.info("释放分布式锁,key:{},value:{}", sysJob.getInvokeTarget(), sysJob.getLockValue());
            SpringUtils.getBean(RedisCache.class).releaseLock(sysJob.getInvokeTarget(), sysJob.getLockValue());
        }

        if (!sysJob.isAllow()) {
            // 如果未获取到执行权限，不记录日志
            return;
        }

        final SysJobLog sysJobLog = new SysJobLog();
        sysJobLog.setJobName(sysJob.getJobName());
        sysJobLog.setJobGroup(sysJob.getJobGroup());
        sysJobLog.setInvokeTarget(sysJob.getInvokeTarget());
        sysJobLog.setStartTime(startTime);
        sysJobLog.setStopTime(new Date());
        long runMs = sysJobLog.getStopTime().getTime() - sysJobLog.getStartTime().getTime();
        sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时：" + runMs + "毫秒");
        if (e != null) {
            sysJobLog.setStatus(Constants.FAIL);
            String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000);
            sysJobLog.setExceptionInfo(errorMsg);
        } else {
            sysJobLog.setStatus(Constants.SUCCESS);
        }

        // 写入数据库当中
        // TODO rms暂时不写job日志，太多了
//        SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog);

        // 更新sysJob信息
        if ("0".equals(sysJob.getIsClustered())) {
            log.info("设置job下次执行时间:" + DateUtils.getStandardTime(context.getNextFireTime()));
            sysJob.setNextFireTime(context.getNextFireTime());
            SpringUtils.getBean(SysJobMapper.class).updateNextFireTime(sysJob);
        }
    }

    /**
     * 执行方法，由子类重载
     *
     * @param context 工作执行上下文对象
     * @param sysJob  系统计划任务
     * @throws Exception 执行过程中的异常
     */
    protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception;
}
